package org.go.work.self;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.List;

import org.go.Go;
import org.go.GoContext;
import org.go.expcetion.GoException;
import org.go.expcetion.JobExecutionException;
import org.go.expcetion.WorkExecutionException;
import org.go.log.Log;
import org.go.log.LoggerFactory;
import org.go.work.WorkDataMap;

/**
 * <p> Built in work for executing native executables in a separate process.</p> 
 * 
 * <pre>
 *             JobDetail job = new JobDetail("dumbJob", null, org.quartz.jobs.NativeJob.class);
 *             job.getJobDataMap().put(org.quartz.jobs.NativeJob.PROP_COMMAND, "echo \"hi\" >> foobar.txt");
 *             Trigger trigger = TriggerUtils.makeSecondlyTrigger(5);
 *             trigger.setName("dumbTrigger");
 *             sched.scheduleJob(job, trigger);
 * </pre>
 * 
 * If PROP_WAIT_FOR_PROCESS is true, then the Integer exit value of the process
 * will be saved as the job execution result in the JobExecutionContext.
 * 
 * @see #PROP_COMMAND
 * @see #PROP_PARAMETERS
 * @see #PROP_WAIT_FOR_PROCESS
 * @see #PROP_CONSUME_STREAMS
 * 
 * @author hejie
 */
public class NativeWork implements Go {
	/**
	 * 
	 */
	private static final long serialVersionUID = 930320827668491096L;

	/**
	 * Consumes data from the given input stream until EOF and prints the data to stdout
	 *
	 * @author cooste
	 * @author jhouse
	 */
	class StreamConsumer extends Thread {
		InputStream is;
		String type;

		/**
		 *
		 */
		public StreamConsumer(InputStream inputStream, String type) {
			this.is = inputStream;
			this.type = type;
		}

		/**
		 * Runs this object as a separate thread, printing the contents of the InputStream
		 * supplied during instantiation, to either stdout or stderr
		 */
		@Override
		public void run() {
			BufferedReader br = null;
			try {
				br = new BufferedReader(new InputStreamReader(is));
				String line = null;

				while ((line = br.readLine()) != null) {
					if (type.equalsIgnoreCase("stderr")) {
						log.warn(type + ">" + line);
					} else {
						log.info(type + ">" + line);
					}
				}
			} catch (IOException ioe) {
				log.error("Error consuming " + type + " stream of spawned process.", ioe);
			} finally {
				if (br != null) {
					try {
						br.close();
					} catch (Exception ignore) {
					}
				}
			}
		}
	}

	static final Log log = LoggerFactory.getLog(NativeWork.class);

	/**
	 * Required parameter that specifies the name of the command (executable) 
	 * to be ran.
	 */
	public static final String PROP_COMMAND = "command";

	/**
	 * Optional parameter (value should be 'true' or 'false') that specifies 
	 * whether the spawned process's stdout and stderr streams should be 
	 * consumed.  If the process creates output, it is possible that it might
	 * 'hang' if the streams are not consumed.
	 * 
	 * <p>Defaults to <code>false</code>.</p>  
	 */
	public static final String PROP_CONSUME_STREAMS = "consumeStreams";

	/**
	 * Optional parameter that specifies the parameters to be passed to the
	 * executed command.
	 */
	public static final String PROP_PARAMETERS = "parameters";

	/**
	 * Optional parameter (value should be 'true' or 'false') that specifies 
	 * whether the job should wait for the execution of the native process to 
	 * complete before it completes.
	 * 
	 * <p>Defaults to <code>true</code>.</p>  
	 */
	public static final String PROP_WAIT_FOR_PROCESS = "waitForProcess";

	private Integer runNativeCommand(String command, String parameters, boolean wait, boolean consumeStreams) throws JobExecutionException {

		String[] cmd = null;
		String[] args = new String[2];
		Integer result = null;
		args[0] = command;
		args[1] = parameters;

		try {
			//with this variable will be done the swithcing
			String osName = System.getProperty("os.name");

			// specific for Windows
			if (osName.startsWith("Windows")) {
				cmd = new String[args.length + 2];
				if (osName.equals("Windows 95")) { // windows 95 only
					cmd[0] = "command.com";
				} else {
					cmd[0] = "cmd.exe";
				}
				cmd[1] = "/C";
				for (int i = 0; i < args.length; i++) {
					cmd[i + 2] = args[i];
				}
			} else if (osName.equals("Linux")) {
				//if (cmd == null) {
				cmd = new String[3];
				//}
				cmd[0] = "/bin/sh";
				cmd[1] = "-c";
				cmd[2] = args[0] + " " + args[1];
			} else { // try this... 
				cmd = args;
			}

			Runtime rt = Runtime.getRuntime();
			// Executes the command
			log.info("About to run " + cmd[0] + " " + cmd[1] + " " + (cmd.length > 2 ? cmd[2] : "") + " ...");
			Process proc = rt.exec(cmd);
			// Consumes the stdout from the process
			StreamConsumer stdoutConsumer = new StreamConsumer(proc.getInputStream(), "stdout");

			// Consumes the stderr from the process
			if (consumeStreams) {
				StreamConsumer stderrConsumer = new StreamConsumer(proc.getErrorStream(), "stderr");
				stdoutConsumer.start();
				stderrConsumer.start();
			}

			if (wait) {
				result = Integer.valueOf(proc.waitFor());
			}
			// any error message?

		} catch (Exception x) {
			throw new JobExecutionException("Error launching native command: ", x, false);
		}

		return result;
	}

	/*
	 * (non-Javadoc)
	 * @see org.go.Go#doWork(org.go.GoContext)
	 */
	@SuppressWarnings("finally")
	@Override
	public Boolean doWork(GoContext context) throws WorkExecutionException {
		WorkDataMap data = context.getMergedJobDataMap();

		String command = data.getString(PROP_COMMAND);

		String parameters = data.getString(PROP_PARAMETERS);

		if (parameters == null) {
			parameters = "";
		}

		boolean wait = true;
		if (data.containsKey(PROP_WAIT_FOR_PROCESS)) {
			wait = data.getBooleanValue(PROP_WAIT_FOR_PROCESS);
		}
		boolean consumeStreams = false;
		if (data.containsKey(PROP_CONSUME_STREAMS)) {
			consumeStreams = data.getBooleanValue(PROP_CONSUME_STREAMS);
		}

		try {
			Integer exitCode = this.runNativeCommand(command, parameters, wait, consumeStreams);
			context.setResult(exitCode);
		} catch (Exception e) {
			throw new WorkExecutionException(e);
		} finally {
			return true;
		}
	}

	@Override
	public List selectWork(String taskParameter, String ownSign, int taskItemNum, List taskItemList, int eachFetchDataNum) throws GoException {
		// TODO Auto-generated method stub
		return null;
	}
}
